#include "main.h"
#include <SDL.h>

#define LOGFILE "gameripper.log"

char *lng = NULL, verbose = 0, decrypt = 0, quit = 0;
#ifdef __WIN32__
#include <windows.h>
#include <winnls.h>
#include <fileapi.h>
#include <shlobj.h>
#include <shellapi.h>
#include <io.h>
HINSTANCE hWinInstance;
HWND hwnd = NULL;
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    FILE *f;
    int argc = 1;
    char fn[PATH_MAX + FILENAME_MAX + 1] = { 0 };
    char *argv[3] = { "gameripper.exe", NULL, NULL };
    wchar_t *arg = GetCommandLineW();
    (void)hPrevInstance;
    (void)lpCmdLine;
    (void)nCmdShow;
    hWinInstance = hInstance;
    switch(GetUserDefaultLangID() & 0xFF) {
        case 0x01: lng = "ar"; break; case 0x02: lng = "bg"; break; case 0x03: lng = "ca"; break; case 0x04: lng = "zh"; break;
        case 0x05: lng = "cs"; break; case 0x06: lng = "da"; break; case 0x07: lng = "de"; break; case 0x08: lng = "el"; break;
        case 0x0A: lng = "es"; break; case 0x0B: lng = "fi"; break; case 0x0C: lng = "fr"; break; case 0x0D: lng = "he"; break;
        case 0x0E: lng = "hu"; break; case 0x0F: lng = "is"; break; case 0x10: lng = "it"; break; case 0x11: lng = "ja"; break;
        case 0x12: lng = "ko"; break; case 0x13: lng = "nl"; break; case 0x14: lng = "no"; break; case 0x15: lng = "pl"; break;
        case 0x16: lng = "pt"; break; case 0x17: lng = "rm"; break; case 0x18: lng = "ro"; break; case 0x19: lng = "ru"; break;
        case 0x1A: lng = "hr"; break; case 0x1B: lng = "sk"; break; case 0x1C: lng = "sq"; break; case 0x1D: lng = "sv"; break;
        case 0x1E: lng = "th"; break; case 0x1F: lng = "tr"; break; case 0x20: lng = "ur"; break; case 0x21: lng = "id"; break;
        case 0x22: lng = "uk"; break; case 0x23: lng = "be"; break; case 0x24: lng = "sl"; break; case 0x25: lng = "et"; break;
        case 0x26: lng = "lv"; break; case 0x27: lng = "lt"; break; case 0x29: lng = "fa"; break; case 0x2A: lng = "vi"; break;
        case 0x2B: lng = "hy"; break; case 0x2D: lng = "bq"; break; case 0x2F: lng = "mk"; break; case 0x36: lng = "af"; break;
        case 0x37: lng = "ka"; break; case 0x38: lng = "fo"; break; case 0x39: lng = "hi"; break; case 0x3A: lng = "mt"; break;
        case 0x3C: lng = "gd"; break; case 0x3E: lng = "ms"; break; case 0x3F: lng = "kk"; break; case 0x40: lng = "ky"; break;
        case 0x45: lng = "bn"; break; case 0x47: lng = "gu"; break; case 0x4D: lng = "as"; break; case 0x4E: lng = "mr"; break;
        case 0x4F: lng = "sa"; break; case 0x53: lng = "kh"; break; case 0x54: lng = "lo"; break; case 0x56: lng = "gl"; break;
        case 0x5E: lng = "am"; break; case 0x62: lng = "fy"; break; case 0x68: lng = "ha"; break; case 0x6D: lng = "ba"; break;
        case 0x6E: lng = "lb"; break; case 0x6F: lng = "kl"; break; case 0x7E: lng = "br"; break; case 0x92: lng = "ku"; break;
        case 0x09: default: lng = "en"; break;
    }
    AttachConsole(ATTACH_PARENT_PROCESS);
    f = _fdopen(_open_osfhandle((intptr_t)GetStdHandle(STD_OUTPUT_HANDLE), 0x4000), "w");
    if(f) { *stdout = *f; *stderr = *f; }
    printf("\r\n"); fflush(stdout);
    SDL_SetMainReady();
    argv[1] = fn;
    if(arg && *arg) {
        while(*arg == L' ') arg++;
        if(*arg == L'\"') {
            for(arg++; *arg && *arg != L'\"'; arg++){} arg++;
        } else {
            while(*arg && *arg != ' ') arg++;
        }
        if(*arg == L' ') {
            while(*arg == L' ') arg++;
            if((arg[0] == L'/' || arg[0] == L'-') && (arg[1] == L'v' || arg[1] == L'V' || arg[1] == L'd' || arg[1] == L'D')) {
                if(arg[1] == L'v' || arg[1] == L'V') verbose++; else decrypt = 1;
                while(*arg && *arg != ' ') arg++;
                while(*arg == ' ') arg++;
            }
            if(*arg) {
                if(*arg == L'\"') arg++;
                WideCharToMultiByte(CP_UTF8, 0, arg, -1, fn, PATH_MAX + FILENAME_MAX, NULL, NULL);
                if(arg[-1] == L'\"') fn[strlen(fn) - 1] = 0;
                argc++;
            }
        }
    }
    exit(main(argc, argv));
    return 0;
}
#endif
#include <SDL_syswm.h>
#include "data.h"

typedef struct {
    int x, id;
} tabpos_t;
tabpos_t tabpos[32];

SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_Texture *screen = NULL, *tabs = NULL, *logo = NULL, *sprite = NULL, *map = NULL;
SDL_Event event;
SDL_Rect fields[64] = { 0 }, sprrect = { 0 }, maprect = { 0 };
static FILE *lf = NULL;
int field = -1, pressedfield = -1;
int crop_x, crop_y, crop_w, crop_h, mx, my, keyinput = 0;
int screen_w = 800, screen_h = 600, screen_p = 0, refresh = 1, loaded = 0, tab = TAB_INFO;
uint32_t ticks, *screen_d = NULL;
char **lang = NULL;
uint64_t pbar_cur = 0, pbar_total = 0;
int pbar_msg = 0;
int snprintf(char *dst, size_t maxlen, const char* fmt, ...);
extern SDL_AudioDeviceID audio_dev;
extern int insplit;

/**
 * Print debug message (only if started with -v)
 */
void debug(char *fmt, ...)
{
    __builtin_va_list args;
#ifdef __WIN32__
    char tmp[256], *s, *d;
    if(verbose) {
        for(s = fmt, d = tmp; *s; s++, d++) {
            if(*s == '%' && s[1] == 'l') { *d++ = *s++; *d++ = 'I'; *d++ = '6'; *d++ = '4'; }
            else *d = *s;
        }
        *d = 0;
        __builtin_va_start(args, fmt);
        if(lf) { vfprintf(lf, tmp, args); fprintf(lf, "\r\n"); fflush(lf); }
        __builtin_va_end(args);
        if(stdout) {
            __builtin_va_start(args, fmt);
            vfprintf(stdout, tmp, args); fprintf(stdout, "\r\n"); fflush(stdout);
            __builtin_va_end(args);
        }
    }
#else
    if(verbose) {
        __builtin_va_start(args, fmt);
        if(lf) { vfprintf(lf, fmt, args); fprintf(lf, "\r\n"); fflush(lf); }
        __builtin_va_end(args);
        if(stdout) {
            __builtin_va_start(args, fmt);
            vfprintf(stdout, fmt, args); fprintf(stdout, "\r\n"); fflush(stdout);
            __builtin_va_end(args);
        }
    }
#endif
}

/**
 * Display status with a progress bar
 */
void status(int msg, uint64_t cur, uint64_t tot)
{
    pbar_msg = msg;
    pbar_cur = cur;
    pbar_total = tot;
    refresh = 1;
}

/**
 * Display a message box
 */
void msgbox(int error, int msg, char *param)
{
#ifdef __WIN32__
    char tmp[1024];
    wchar_t buf[1024];

    debug("msgbox %s %s '%s'", error ? "error" : "info", lang[msg], param && *param ? param : "");
    if(param && *param) {
        snprintf(tmp, sizeof(tmp) - 1, "%s\r\n%s", lang[msg], param);
        MultiByteToWideChar(CP_UTF8, 0, tmp, -1, buf, sizeof(buf));
    } else
        MultiByteToWideChar(CP_UTF8, 0, lang[msg], -1, buf, sizeof(buf));
    MessageBoxW(hwnd, buf, L"GAME RIPPER", (error ? MB_ICONEXCLAMATION : MB_ICONINFORMATION) | MB_OK);
#else
    SDL_DisplayMode dm;
    SDL_Window *window = NULL;
    SDL_Surface *surface = NULL;
    int w, h;

    debug("msgbox %s: %s '%s'", error ? "error" : "info", lang[msg], param && *param ? param : "");
    /* create a dummy MessageBox on Linux */
    w = textlen(lang[msg]) + 48;
    h = textlen(param) + 48; if(w < h) w = h;
    h = 64 + (param && *param ? 16 : 0);
    SDL_GetDesktopDisplayMode(0, &dm);
    if((window = SDL_CreateWindow("GAME RIPPER", (dm.w - w) / 2, (dm.h - 64) / 2, w, h,
        SDL_WINDOW_ALWAYS_ON_TOP | SDL_WINDOW_SKIP_TASKBAR)) && (surface = SDL_GetWindowSurface(window))) {
        memset(surface->pixels, 0xAA, h * surface->pitch);
        text2(0, 0, w, h, surface->pitch / 4, (uint32_t*)surface->pixels, 24, 24, 0xff000000, lang[msg]);
        if(param && *param) text2(0, 0, w, h, surface->pitch / 4, (uint32_t*)surface->pixels, 24, 40, 0xff000000, param);
        while(1) {
            SDL_UpdateWindowSurface(window);
            SDL_RaiseWindow(window);
            SDL_WaitEvent(&event);
            if(event.type == SDL_QUIT || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_KEYUP ||
                (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE)) break;
        }
        SDL_DestroyWindow(window);
    }
#endif
}

/**
 * Recalculate available tabs and their positions on screen
 */
void refreshtabs(void)
{
    int i, j, l, p, x, y;
    uint32_t *d, *dst;

    memset(tabpos, 0, sizeof(tabpos));
    if(tabs) {
        SDL_LockTexture(tabs, NULL, (void*)&d, &p);
        memset(d, 0, p * 20); p >>= 2;
        for(i = j = l = 0; j < TAB_CHEATS - TAB_INFO + 1; j++)
            if(!j || (engine_features & (1 << (j + 1)))) {
                tabpos[i].x = !i ? 4 : tabpos[i - 1].x + l + 5;
                dst = d + tabpos[i].x;
                l = textlen(lang[INFO + j]) + 7;
                if(tab == TAB_INFO + j) {
                    for(x = 1; x < l && tabpos[i].x + x < screen_w; x++) dst[x] = BG;
                    for(dst += p, y = 1; y < 20; y++, dst += p)
                        for(x = 0; x <= l && tabpos[i].x + x < screen_w; x++) dst[x] = BG;
                    text2(0, 0, screen_w, 20, p, d, tabpos[i].x + 4, 2, FG, lang[INFO + j]);
                } else {
                    for(dst += p + p, x = 1; x < l && tabpos[i].x + x < screen_w; x++) dst[x] = BG;
                    for(dst += p, y = 3; y < 20; y++, dst += p) {
                        if(tabpos[i].x < screen_w) dst[0] = BG;
                        for(x = 1; x < l && tabpos[i].x + x < screen_w; x++) dst[x] = FG;
                        if(tabpos[i].x + l < screen_w) dst[l] = BG;
                    }
                    text2(0, 0, screen_w, 20, p, d, tabpos[i].x + 4, 4, BG, lang[INFO + j]);
                }
                tabpos[i++].id = TAB_INFO + j;
            }
        tabpos[i].x = tabpos[i - 1].x + l + 5;
        SDL_UnlockTexture(tabs);
    }
}

/**
 * Resize window
 */
int resizewin(int w, int h)
{
    if(w < 64) w = 64;
    if(h < 64) h = 64;
    if(tabs) { SDL_DestroyTexture(tabs); tabs = NULL; }
    if(screen) { SDL_DestroyTexture(screen); screen = NULL; }
    screen_w = w - 32; screen_h = h - 52;
    if(!(screen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, screen_w, screen_h))) return 0;
    if((tabs = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, screen_w, 20))) {
        refreshtabs();
        SDL_SetTextureBlendMode(tabs, SDL_BLENDMODE_BLEND);
    }
    SDL_RaiseWindow(window);
    refresh = 1;
    return 1;
}

/**
 * Common UI controller dispatcher
 */
void main_ctrl(void)
{
    switch(tab) {
        case TAB_INFO: if(keyinput) engine_keyinput_ctrl(); else engine_info_ctrl(); break;
        case TAB_SPRITES: sprites_ctrl(); break;
        case TAB_MODELS: models_ctrl(); break;
        case TAB_MAPS: maps_ctrl(); break;
        case TAB_SFX: audio_ctrl(); break;
        case TAB_BGM: audio_ctrl(); break;
        case TAB_TEXTS: texts_ctrl(); break;
        case TAB_VIDEOS: videos_ctrl(); break;
    }
}

/**
 * Entry point
 */
int main(int argc, char **argv)
{
    char *ver = "**** GAME RIPPER ROM V" GR_VER " ****", *lnk = "ahuvsw?)(ff~jnxi!\177cu<st{rjpjkyo1xALGQMUVBZ)";
    char *arg = NULL, tmp[256];
    stbi__context s;
    stbi__result_info ri;
    SDL_Rect src, dst;
    SDL_Surface *srf;
    SDL_SysWMinfo wmInfo = { 0 };
    SDL_AudioSpec wanted = { 0 };
    int i, x, y, p, currlang, working, dy = 0;
    uint8_t *data, *buf, *sbuf;
    int32_t diff;
    uint32_t pal[16] = { 0x000000, 0x626262, 0x898989, 0xadadad, 0xffffff, 0x444e9f, 0x757ecb, 0x12546d, 0x3c68a1,
        0x87d4c9, 0x9be29a, 0x5eab5c, 0xc6bf6a, 0xcb7e88, 0x9b4550, 0xa357a0 };

    if(argc > 1) {
        for(arg = NULL, i = 1; i < argc && !arg; i++) {
            if(argv[i][0] == '-') {
                for(x = 1; argv[i][x]; x++) {
                    if(argv[i][x] == 'v') verbose++;
                    if(argv[i][x] == 'd') decrypt = 1;
                }
            } else arg = argv[i];
        }
        if(!arg) arg = argv[1];
    }
    if(decrypt) engine_backup = engine_convert = 1;
    lf = NULL;

    /* initialize window */
    if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_TIMER|SDL_INIT_EVENTS)) {
        fprintf(stderr, "unable to initialize SDL");
        return 1;
    }
    if(!(window = SDL_CreateWindow("GAME RIPPER", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_w, screen_h, SDL_WINDOW_RESIZABLE))) goto err;
    if(!(renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED)) && !(renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE))) goto err;
    s.read_from_callbacks = 0;
    s.img_buffer = s.img_buffer_original = icon_png;
    s.img_buffer_end = s.img_buffer_original_end = icon_png + sizeof(icon_png);
    ri.bits_per_channel = 8;
    if((buf = (uint8_t*)stbi__png_load(&s, &x, &y, &p, 4, &ri))) {
        if((srf = SDL_CreateRGBSurfaceFrom((Uint32 *)buf, x, y, 32, x * 4, 0xFF, 0xFF00, 0xFF0000, 0xFF000000))) {
            SDL_SetWindowIcon(window, srf);
            SDL_FreeSurface(srf);
        }
        free(buf);
    }
    SDL_SetRenderDrawColor(renderer, FG & 0xff, (FG >> 8) & 0xff, (FG >> 16) & 0xff, 255);
    SDL_RenderClear(renderer);
    SDL_RenderPresent(renderer);
    SDL_GetWindowWMInfo(window, &wmInfo);
#ifdef __WIN32__
    hwnd = wmInfo.info.win.window;
#endif

    /* get our assets */
    if(!lng) lng = getenv("LANG");
    if(!lng || !*lng) lng = "en";
    lang = NULL;
    for(y = 0; y < (int)(sizeof(i18n)/sizeof(i18n[0])); y += TEXT_LEN)
        if(!memcmp(lng, i18n[y], 5)) { lang = &i18n[y]; currlang = y; break; }
    if(!lang)
        for(y = 0; y < (int)(sizeof(i18n)/sizeof(i18n[0])); y += TEXT_LEN)
            if(!memcmp(lng, i18n[y], 2)) { lang = &i18n[y]; currlang = y; break; }
    if(!lang) { lang = &i18n[0]; currlang = 0; }
    s.read_from_callbacks = 0;
    s.img_buffer = s.img_buffer_original = logo_png;
    s.img_buffer_end = s.img_buffer_original_end = logo_png + sizeof(logo_png);
    ri.bits_per_channel = 8;
    if(!(unicodes = (uint32_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)unicodes_gz, sizeof(unicodes_gz), UNICODES_LEN, &p, 1)) ||
       !(glyphs = (uint8_t*)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)glyphs_gz, sizeof(glyphs_gz), GLYPHS_LEN, &p, 1)) ||
       !(buf = (uint8_t*)stbi__png_load(&s, &x, &y, &p, 4, &ri))) goto err;
    numunicodes = UNICODES_LEN / 4;
    if((logo = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, 768, 128))) {
        SDL_LockTexture(logo, NULL, (void*)&data, &p);
        sbuf = buf + ((time(NULL) & 3) << 7) * 768 * 4;
        for(y = 0; y < 128; y++, sbuf += 768 * 4, data += p)
            memcpy(data, sbuf, 768 * 4);
        SDL_UnlockTexture(logo);
        SDL_SetTextureBlendMode(logo, SDL_BLENDMODE_BLEND);
        free(buf); buf = NULL;
    }
    srand(time(NULL));
    if(!resizewin(screen_w, screen_h)) goto err;
    if(verbose && (lf = fopen(LOGFILE, "wb")))
        debug("%s", ver);

    /* initialize audio */
    wanted.freq = 44100;
    wanted.format = AUDIO_S16;
    wanted.channels = 2;
    wanted.samples = 1024;
    if(!(audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted, NULL, SDL_AUDIO_ALLOW_ANY_CHANGE)))
        debug("unable to initialize audio device");

    /* main event loop */
    while(!quit) {
        working = pbar_msg && pbar_total > 0;
        ticks = SDL_GetTicks();
        SDL_GetMouseState(&mx, &my);
        screen_d = NULL; screen_p = 0;
        for(field = -1, i = 0; i < (int)(sizeof(fields)/sizeof(fields[0])) && field == -1; i++)
            if(mx >= 16 + fields[i].x && my >= fields[i].y + dy && mx < 16 + fields[i].x + fields[i].w && my < fields[i].y + fields[i].h + dy)
                field = i;
        if(mx >= 16 + screen_w - 10 && mx < 16 + screen_w && my >= 36 && my < 36 + screen_h)
            field = 999;
        while(SDL_PollEvent(&event)) {
            /* handle common events here, pass everything else to main_ctrl() */
            switch(event.type) {
                case SDL_QUIT: quit = 1; break;
                case SDL_WINDOWEVENT:
                    switch(event.window.event) {
                        case SDL_WINDOWEVENT_CLOSE: quit = 1; break;
                        case SDL_WINDOWEVENT_RESIZED: case SDL_WINDOWEVENT_SIZE_CHANGED:
                            if(!resizewin(event.window.data1, event.window.data2)) goto err;
                        break;
                    }
                break;
                case SDL_KEYDOWN:
                    if(!working) {
                        switch(event.key.keysym.sym) {
                            case SDLK_ESCAPE:
                                if(keyinput) keyinput = 0; else
                                if(insplit) {
                                    sprrect.w = sprrect.h = insplit = 0;
                                    sprites_update();
                                } else quit = 1;
                            break;
                            case SDLK_F1:
                                currlang += TEXT_LEN;
                                if(currlang >= (int)(sizeof(i18n)/sizeof(i18n[0]))) currlang = 0;
                                lang = &i18n[currlang];
                                refreshtabs();
                            break;
                            case SDLK_F2:
                                if(!loaded) {
                                    tab = TAB_INFO;
                                    keyinput ^= 1;
                                }
                            break;
                            case SDLK_TAB:
                                audio_stop();
                                for(i = 0; (!i || tabpos[i].id) && tabpos[i].id != tab; i++);
                                tab = tabpos[i + 1].id;
                            break;
                            case SDLK_SPACE:
                            case SDLK_RETURN:
                                if(!loaded) {
                                    if(keyinput) {
                                        keyinput = 0;
                                    } else {
                                        opendialog(LOADDIR, 1);
                                        if(fn[0]) engine_init(fn);
                                    }
                                    event.key.keysym.sym = 0;
                                }
                            break;
                        }
                        main_ctrl();
                        refresh = 1;
                    } else
                    if(event.key.keysym.sym == SDLK_ESCAPE) quit = 1;
                break;
                case SDL_KEYUP:
                    if(!working) {
                        main_ctrl();
                        pressedfield = field = -1;
                        refresh = 1;
                    }
                break;
                case SDL_MOUSEBUTTONDOWN:
                    if(!working && loaded) {
                        if(my < 36) {
                            audio_stop();
                            for(i = 0; i < TAB_CHEATS - TAB_INFO + 1 && tabpos[i].x; i++)
                                if(mx >= 16 + tabpos[i].x && mx < 12 + tabpos[i + 1].x) {
                                    tab = tabpos[i].id; refreshtabs(); break;
                                }
                        } else
                        if(field == 999) {
                            scrdel = my - 36 - scrpos;
                        } else main_ctrl();
                        pressedfield = field;
                        refresh = 1;
                    }
                break;
                case SDL_MOUSEBUTTONUP:
                    if(!working) {
                        if(!loaded) {
                            opendialog(LOADDIR, 1);
                            if(fn[0]) engine_init(fn);
                        } else
                        if(pressedfield != 999) main_ctrl();
                        pressedfield = -1;
                        refresh = 1;
                    }
                break;
                case SDL_MOUSEWHEEL:
                    if(!working) {
                        if(event.wheel.y > 0) scrcur -= 32; else scrcur += 32;
                        if(scrcur < 0) scrcur = 0;
                        if(scrcur > scrtot) scrcur = scrtot;
                        refresh = 1;
                    }
                break;
                case SDL_MOUSEMOTION:
                    if(!working) {
                        if(pressedfield == 999) {
                            x = screen_h - scrbtn;
                            if(x < 1) x = 1;
                            y = my - 36 - scrdel;
                            if(y < 0) y = 0;
                            if(y > x) y = x;
                            scrcur = y * scrtot / x;
                            if(scrcur > scrtot) scrcur = scrtot;
                            refresh = 1;
                        } else main_ctrl();
                    }
                break;
                case SDL_DROPFILE:
                    if(!working && event.drop.file) {
                        engine_init(!memcmp(event.drop.file, "file://", 7) ? event.drop.file + 7 : event.drop.file);
                        refresh = 1;
                    }
                break;
            }
        }
        /* overscan effect */
        if(working) {
            src.x = 0; src.w = screen_w + 32;
            for(src.y = 0; src.y < screen_h + 52; src.y += src.h) {
                x = rand() & 15;
                SDL_SetRenderDrawColor(renderer, pal[x] & 0xff, (pal[x] >> 8) & 0xff, (pal[x] >> 16) & 0xff, 255);
                src.h = 4 + 2 * (rand() & 15);
                SDL_RenderFillRect(renderer, &src);
            }
        } else {
            SDL_SetRenderDrawColor(renderer, FG & 0xff, (FG >> 8) & 0xff, (FG >> 16) & 0xff, 255);
            SDL_RenderClear(renderer);
        }
        /* draw screen */
        if(screen) {
            /* normally we just display textures, but if they need an update, call UI view functions */
            if(refresh) {
                refreshtabs();
                refresh = 0;
                SDL_LockTexture(screen, NULL, (void*)&screen_d, &screen_p);
                screen_p >>= 2;
                for(y = 0; y < screen_p * screen_h; y++) screen_d[y] = BG;
                memset(fields, 0, sizeof(fields));
                crop_x = crop_y = 0; crop_w = screen_w; crop_h = screen_h;
                if(working) {
                    /* progress bar, displayed when worker thread is, well, working */
                    sprintf(tmp, "%s... %u%%", lang[pbar_msg], (uint32_t)(pbar_total ? pbar_cur * 100 / pbar_total : 0));
                    y = loaded ? (screen_h - 20) / 2 : (screen_h - 128) / 2;
                    text((screen_w - textlen(tmp)) / 2, y - 18, FG, tmp);
                    i = (int)(pbar_total ? pbar_cur * (screen_w - 16) / pbar_total : 0);
                    box(8 + i, y + 2, screen_w - 16 - i, 16, BG - 0x080808);
                    box(8, y + 2, i, 16, FG);
                    engine_view_ram();
                } else
                if(loaded) {
                    /* normal mode, call UI view */
                    switch(tab) {
                        case TAB_INFO: engine_info_view(); break;
                        case TAB_SPRITES: sprites_view(); break;
                        case TAB_MODELS: models_view(); break;
                        case TAB_MAPS: maps_view(); break;
                        case TAB_SFX: audio_view(); break;
                        case TAB_BGM: audio_view(); break;
                        case TAB_VIDEOS: videos_view(); break;
                        case TAB_TEXTS: texts_view(); break;
                    }
                } else {
                    /* about window */
                    if(keyinput)
                        engine_keyinput_view();
                    else {
                        for(i = 0; lnk[i]; i++) tmp[i] = lnk[i + 1] ^ i;
                        text((screen_w - textlen(ver)) / 2, 4, FG, ver);
                        text((screen_w - textlen(lnk)) / 2, 20, FG, tmp);
                        x = text(4, 44, FG, "[F1]") + 8;
                        text(x, 44, FG, "language, habla, Sprache, Γλώσσα, הפש, язык, 언어, 语言, 言語");
                        x = text(4, 60, FG, "[F2]") + 8;
                        text(x, 60, FG, lang[KEYHELP]);
                        text(4, 80, FG, lang[WELCOME]);
                        for(i = 0, y = 96; i < nummod; i++, y += 16) {
                            x = text(4, y, FG, " - ");
                            x = text(x, y, FG, engine_modules[i].name);
                            x = text(x, y, BG + 0x282828, " (");
                            x = text(x, y, BG + 0x282828, engine_modules[i].mod);
                            x = text(x, y, BG + 0x282828, ")");
                        }
                        text(4, y, FG, "READY.");
                    }
                }
                SDL_UnlockTexture(screen);
            }
            /* display textures */
            dst.x = dst.y = 16; src.x = src.y = 0; src.w = dst.w = screen_w; src.h = dst.h = screen_h;
            if(loaded) {
                if(tabs) {
                    src.h = dst.h = 20;
                    SDL_RenderCopy(renderer, tabs, &src, &dst);
                    src.h = dst.h = screen_h; dst.y = 36;
                }
                if(tab == TAB_SPRITES && screen_w > 78 && screen_h > 24 && sprite)
                    sprites_update();
            } else {
                if(logo) {
                    src.w = dst.w = 768; src.h = dst.h = 128; dst.x = (screen_w + 32 - dst.w) / 2; dst.y = 6;
                    SDL_RenderCopy(renderer, logo, &src, &dst);
                    src.w = dst.w = screen_w; src.h = dst.h = screen_h + 32 - 6 - 128; dst.x = 16; dst.y = 8 + 128;
                }
            }
            dy = dst.y;
            SDL_RenderCopy(renderer, screen, &src, &dst);
            if(loaded) {
                if(tab == TAB_SPRITES && screen_w > 78 && screen_h > 24) {
                    if(sprite && sprrect.w > 0 && sprrect.h > 0) {
                        dst.w = screen_w - 80; dst.h = sprrect.h * dst.w / sprrect.w;
                        if(dst.h > (screen_h - 26)) { dst.h = screen_h - 26; dst.w = sprrect.w * dst.h / sprrect.h; }
                        dst.x = 17 + (screen_w - 80 - dst.w) / 2; dst.y = 37 + (screen_h - 26) - dst.h;
                        SDL_RenderCopy(renderer, sprite, &sprrect, &dst);
                    }
                }
                if(tab == TAB_MAPS && map && maprect.w > 0 && maprect.h > 0) {
                    if(mapzoom) {
                        src.x = src.y = 0; src.w = maprect.w; src.h = maprect.h;
                        dst.w = screen_w - 14 - mapnw; dst.h = src.h * dst.w / src.w;
                        if(dst.h > (screen_h - 26)) { dst.h = screen_h - 26; dst.w = src.w * dst.h / src.h; }
                        dst.x = 17 + (screen_w - 14 - mapnw - dst.w) / 2; dst.y = 37 + (screen_h - 26 - dst.h) / 2;
                    } else {
                        dst.w = screen_w - 14 - mapnw; dst.h = screen_h - 26;
                        x = maprect.w - maprect.x;
                        y = maprect.h - maprect.y;
                        src.x = (maprect.x < 0 ? 0 : maprect.x); src.w = x > dst.w ? dst.w : x;
                        src.y = (maprect.y < 0 ? 0 : maprect.y); src.h = y > dst.h ? dst.y : y;
                        if(src.x + dst.w > maprect.w) { x = dst.w; src.x = maprect.w - dst.w; if(src.x < 0) src.x = 0; }
                        if(src.y + dst.h > maprect.h) { y = dst.h; src.y = maprect.h - dst.h; if(src.y < 0) src.y = 0; }
                        if(x > dst.w) x = dst.w;
                        if(x > maprect.w) { x = maprect.w; src.x = 0; }
                        if(y > dst.h) y = dst.h;
                        if(y > maprect.h) { y = maprect.h; src.y = 0; }
                        src.w = dst.w = x; src.h = dst.h = y;
                        dst.x = 17; dst.y = 37;
                    }
                    SDL_RenderCopy(renderer, map, &src, &dst);
                }
            }
        }
        SDL_RenderPresent(renderer);
        if(arg) { engine_init(arg); arg = NULL; }
        if(!working && (engine_features & EF_NOMEM)) {
            engine_features &= ~EF_NOMEM;
            msgbox(1, NOMEM, NULL);
        }
        if(!working && loaded && decrypt) break;
        /* run main loop at 60 FPS */
        diff = (1000/60) - (SDL_GetTicks() - ticks);
        if(diff > 0 && diff < 1000) SDL_Delay(diff);
    }

    /* free memory and close opened resources */
err:debug("cleanup");
    engine_stop();
    if(audio_dev) { audio_stop(); SDL_CloseAudioDevice(audio_dev); audio_dev = 0; }
    if(logo) { SDL_DestroyTexture(logo); logo = NULL; }
    if(tabs) { SDL_DestroyTexture(tabs); tabs = NULL; }
    if(screen) { SDL_DestroyTexture(screen); screen = NULL; }
    if(renderer) { SDL_DestroyRenderer(renderer); renderer = NULL; }
    if(window) { SDL_DestroyWindow(window); window = NULL; }
    if(unicodes) { free(unicodes); unicodes = NULL; }
    if(glyphs) { free(glyphs); glyphs = NULL; }
    if(lf) {
        debug("**** FINISHED ****"); fclose(lf); lf = NULL;
#ifdef __WIN32__
        ShellExecuteA(0, 0, LOGFILE, 0, 0 , SW_SHOW );
#endif
    }
    SDL_Quit();
    return 0;
}
